<?php
/**
 *
 * @author 李大牛家的牛 (scafel@163.com)
 * @className Commons.php
 * @createDate 2025/7/27 - 12:45
 * @description
 **/


/**
 * 返回字符串的毫秒数时间戳
 * @return string 毫秒数时间戳
 */
function getTotalMillisecond(): string
{
    $time = microtime(true) * 1000;
    $milliSecond = explode(".", $time);
    return $milliSecond[0];
}

/**
 * @param int $length 随机数长度
 * @return string string 随机数
 */
function getRandomNum(int $length = 10): string
{
    $currentLen = 0;
    $str = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
        't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
    $random = '';
    while ($currentLen < $length) {
        $random .= $str[rand(0, 35)];
        $currentLen++;
    }
    return $random;
}

/**
 * XML编码
 * @param array|object $data 数据
 * @param string $root 根节点名
 * @param string $item 数字索引的子节点名
 * @param string $attr 根节点属性
 * @param string $id   数字索引子节点key转换的属性名
 * @param string $encoding 数据编码
 * @return string
 */
function xmlEnCode(array|object $data, string $root='think', string $item='item', array $attr= array(), string $id='id', string $encoding='utf-8'): string
{
    $_attr = array();
    foreach ($attr as $key => $value) {
        $_attr[] = "{$key}=\"{$value}\"";
    }
    $attrStr = implode(' ', $_attr);
    $attrStr   = trim($attrStr);
    $attrStr   = empty($attrStr) ? '' : " {$attrStr}";
    $xml    = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
    $xml   .= "<{$root}{$attrStr}>";
    $xml   .= dataToXml($data, $item, $id);
    $xml   .= "</{$root}>";
    return $xml;
}

/**
 * 数据XML编码
 * @param array|object  $data 数据
 * @param string $item 数字索引时的节点名称
 * @param string $id   数字索引key转换为的属性名
 * @return string
 */
function dataToXml(array|object $data, string $item='item', string $id='id'): string {
    $xml = $attr = '';
    foreach ($data as $key => $val) {
        if(is_numeric($key)){
            $id && $attr = " {$id}=\"{$key}\"";
            $key  = $item;
        }
        $xml    .=  "<{$key}{$attr}>";
        $xml    .=  (is_array($val) || is_object($val)) ? dataToXml($val, $item, $id) : $val;
        $xml    .=  "</{$key}>";
    }
    return $xml;
}

/**
 * 获取客户端IP地址
 * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
 * @param boolean $adv 是否进行高级模式获取（有可能被伪装）
 * @return string|int
 */
function getClientIp(int $type = 0,bool $adv=false) : string|int {
    $type       =  $type ? 1 : 0;
    static $ip  =   NULL;
    if ($ip !== NULL) return $ip[$type];
    if($adv){
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $arr    =   explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $pos    =   array_search('unknown',$arr);
            if(false !== $pos) unset($arr[$pos]);
            $ip     =   trim($arr[0]);
        }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
            $ip     =   $_SERVER['HTTP_CLIENT_IP'];
        }elseif (isset($_SERVER['REMOTE_ADDR'])) {
            $ip     =   $_SERVER['REMOTE_ADDR'];
        }
    }elseif (isset($_SERVER['REMOTE_ADDR'])) {
        $ip     =   $_SERVER['REMOTE_ADDR'];
    }
    // IP地址合法验证
    $long = sprintf("%u",ip2long($ip));
    $ip   = $long ? array($ip, $long) : array('0.0.0.0', 0);
    return $ip[$type];
}

/**
 * 获取客户端浏览器信息
 */
function getClientBrowser(): string {
    $agent = $_SERVER['HTTP_USER_AGENT'];
    return substr($agent,0, 255);
}

/**
 * 将数组转换成where条件需要的结构
 * @param array $data
 * @return array
 */
function dataToWhere(array $data): array
{
    $where = array();
    foreach ($data as $key=>$value){
        $where[] = [$key,'=',$value];
    }
    return $where;
}


/**
 * 自定义加密解密函数
 * @param string $string  需要加密的字符串或数字
 * @param string $operation 判断是加密还是解密   E加密 D解密
 * @param string $key 密钥
 * @return string
 */
function strEncrypt(string $string,string $operation = 'E',string $key='ldn') {
    $key=md5($key);
    $key_length=strlen($key);
    $string =   $operation=='D'?urlSafeBase64decode($string):substr(md5($string.$key),0,8).$string;
    $string_length=strlen($string);
    $rndkey=$box=array();
    $result='';
    for($i=0;$i<=255;$i++){
        $rndkey[$i]=ord($key[$i%$key_length]);
        $box[$i]=$i;
    }
    for($j=$i=0;$i<256;$i++){
        $j=($j+$box[$i]+$rndkey[$i])%256;
        $tmp=$box[$i];
        $box[$i]=$box[$j];
        $box[$j]=$tmp;
    }
    for($a=$j=$i=0;$i<$string_length;$i++){$a=($a+1)%256;$j=($j+$box[$a])%256;$tmp=$box[$a];$box[$a]=$box[$j];
        $box[$j]=$tmp;
        $result.=chr(ord($string[$i])^($box[($box[$a]+$box[$j])%256]));
    }
    if($operation=='D'){
        if(substr($result,0,8)==substr(md5(substr($result,8).$key),0,8)){
            return substr($result,8);
        }else{
            return -404;
        }
    }else{
        return urlSafeBase64encode($result);
    }
}

/**
 * @param string $string 待编码的字符串
 * @return string
 * @intention 不含特殊符号的base64编码
 */
function urlSafeBase64encode(string $string): string {
    $data = base64_encode($string);
    return str_replace(array('+','/','='),array('-','_',''),$data);
}
/**
 * @param string $string 待解码的字符串
 * @return string
 * @intention  不含特殊符号的base64解码
 */
function urlSafeBase64decode(string $string) : string {
    $data = str_replace(array('-', '_'), array('+', '/'), $string);
    $mod4 = strlen($data) % 4;
    if ($mod4) {
        $data .= substr('====', $mod4);
    }
    return base64_decode($data) ?? "";
}

/**
 * @param array $list 需要排序的数组
 * @param string $field 排序的字段
 * @param string $sortBy 排序的方式
 * @return array
 * @intention 对二维数组进行排序
 */
function listSortBy(array $list, string $field, string $sortBy = 'asc') : array {
    $refer = $resultSet = array();
    foreach ($list as $i => $data){
        $refer[$i] = &$data[$field];
    }
    switch ($sortBy){
        case 'asc': // 正向排序
            asort($refer);
            break;
        case 'desc': // 逆向排序
            arsort($refer);
            break;
        case 'nat': // 自然排序
            natcasesort($refer);
            break;
    }
    foreach ($refer as $key => $val){
        $resultSet[] = &$list[$key];
    }
    return $resultSet;
}

/**
 * 将数据转换成树形结构
 * @param array $data  数据
 * @param string $pk 主键
 * @param string $pid 父级字段
 * @param string $child 子级字段
 * @return array 树形结构数据
 */
function dataToTree(array $data, string $pk='id', string $pid='pid', string $child='children'): array
{
    // 创建一个引用数组，以主键为索引
    $refs = array();
    $tree = array();

    foreach ($data as $key => $val) {
        $refs[$val[$pk]] = &$data[$key];
    }

    foreach ($data as $key => $val) {
        if (isset($val[$pid]) && isset($refs[$val[$pid]])) {
            // 如果父节点存在，则将当前节点添加到父节点的子节点中
            $refs[$val[$pid]][$child][] = &$data[$key];
        } else {
            // 否则将当前节点添加到根节点树中
            $tree[] = &$data[$key];
        }
    }
    return $tree;

}
